package com.hup.utils.swing;

import com.hup.utils.algorithm.image.ImageShape;
import com.hup.utils.commons.hardware.ScreenUtil;
import com.sun.awt.AWTUtilities;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinUser;
import lombok.extern.slf4j.Slf4j;
import sun.awt.windows.WComponentPeer;

import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.Field;

/**
 * @author hugan
 * @date 2020/3/13
 */
@Slf4j
public class FrameUtil {

    /**
     * 设置窗口位置
     */
    public static void setLocation(Window w, Gravity gravity) {
        Point location = getLocation(w.getWidth(), w.getHeight(), gravity);
        w.setLocation(location);
    }

    /**
     * 获取基于 gravity的窗口位置
     */
    public static Point getLocation(int winWidth, int winHeight, Gravity gravity) {
        int x = 0, y = 0;
        Insets coor = ScreenUtil.getScreenUsableCoor();
        switch (gravity) {
            case TOP_L:
                break;
            case TOP_CENTER:
                x = (coor.right - coor.left - winWidth) / 2;
                break;
            case TOP_R:
                x = coor.right - winWidth;
                break;
            case MID_L:
                y = (coor.bottom - coor.top - winHeight) / 2;
                break;
            case MID_CENTER:
                x = (coor.right - coor.left - winWidth) / 2;
                y = (coor.bottom - coor.top - winHeight) / 2;
                break;
            case MID_R:
                x = coor.right - winWidth;
                y = (coor.bottom - coor.top - winHeight) / 2;
                break;
            case BOTTOM_L:
                y = coor.bottom - winHeight;
                break;
            case BOTTOM_CENTER:
                x = (coor.right - coor.left - winWidth) / 2;
                y = coor.bottom - winHeight;
                break;
            case BOTTOM_R:
                x = coor.right - winWidth;
                y = coor.bottom - winHeight;
                break;
        }
        return new Point(x, y);
    }

    /**
     * @param window 要正确监听,必须设置setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);或者重写 windowClosing 方法为dispose
     */
    public static void addVisibleListener(Window window, VisibleListener listener) {
        window.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                listener.onHidden(true);//点关闭按钮,dispose()后触发
            }
        });
        window.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentShown(ComponentEvent e) {
                listener.onShown();
            }

            @Override
            public void componentHidden(ComponentEvent e) {
                listener.onHidden(false);//setVisible(false)后触发
            }
        });
    }

    public static void setAlpha(Window win, float alpha) {
        AWTUtilities.setWindowOpacity(win, alpha);
    }

    /**
     * 设置窗口背景透明;
     * 场景:如用于仅显示[gif]图;
     * 效果:点击透明像素时,有鼠标穿透效果!!太好了,且会伴随gif的边界而更新
     */
    public static void setTransparent(Window w) {
        //要先setUndecorated才能设置background,否则报错
        if (w instanceof Frame) {
            ((Frame) w).setUndecorated(true);
        } else if (w instanceof Dialog) {
            ((Dialog) w).setUndecorated(true);
        }
        w.setBackground(new Color(0, 0, 0, 0));
    }

    /**
     * 将窗口形状设为与图片一样
     */
    public static void setIrregular(Window win, Image img) {
        AWTUtilities.setWindowShape(win, ImageShape.getImageShape(img));
    }

    public static Window getParentWindow(Component component) {
        return SwingUtilities.windowForComponent(component);
    }

    /**
     * 判断该窗口是否为无边框的
     */
    public static boolean isUndecorated(Window window) {
        if (window instanceof Frame) return ((Frame) window).isUndecorated();
        if (window instanceof Dialog) return ((Dialog) window).isUndecorated();
        return true;
    }

    /**
     * 实现鼠标穿透,注意看源码!!
     * .本方法中途会"设置并还原"窗口的bounds
     * .窗口alpha必须小于1,才能鼠标穿透,本方法末尾会确保alpha<1
     * .调用本方法后, 后续再[setVisible,setAlpha]都不会使鼠标穿透失效
     * 会使穿透效果失效的api
     * .如: dispose, 暂未测试其他api
     * .dispose后,可以重新调用,恢复鼠标穿透
     *
     * @param window 目前只测试过 {@link JWindow} !!
     * @return [0]=调用本方法前, 窗口的默认属性值(无穿透效果); [1]=鼠标穿透的属性值
     * @throws Exception 不支持鼠标穿透?? 目前未遇到
     */
    public static int[] setMousePenetration(Window window) throws Exception {
        Dimension size0 = window.getSize();
        Point location0 = window.getLocation();
        boolean visible0 = window.isVisible();

        try {
            //必须先显示窗口,hwnd才不是null
            if (!visible0) {
                window.setSize(1, 1);
                window.setLocation(-10, -10);
                window.setVisible(true);
            }
            HWND hwnd = getHwnd(window);

            /*
            鼠标穿透
                https://www.cnblogs.com/xyzyx/p/3447072.html
                直接追加 WS_EX_TRANSPARENT 即可
            属性取值
                https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa251511(v=vs.60)
                WinUser.WS_EX_TRANSPARENT | WinUser.WS_EX_LAYERED// 会使JWindow显示在alt+tab中
                WS_EX_TOOLWINDOW = 0x00000080;// 在alt+tab中隐藏,JNA没收录
             */
            int defValue = User32.INSTANCE.GetWindowLong(hwnd, WinUser.GWL_EXSTYLE);
            int newValue = defValue | WinUser.WS_EX_TRANSPARENT;
            User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE, newValue);

            //alpha必须小于1,才能鼠标穿透
            float alpha0 = AWTUtilities.getWindowOpacity(window);
            if (alpha0 == 1) AWTUtilities.setWindowOpacity(window, 0.999f);
            return new int[]{defValue, newValue};
        } finally {
            //还原bounds
            window.setSize(size0);
            window.setLocation(location0);
            window.setVisible(visible0);
        }
    }

    /**
     * 直接设置窗口属性,目前用于"实现/取消鼠标穿透"
     *
     * @param value 如 {@link #setMousePenetration(Window)}
     * @apiNote 实际使用有问题! 会变黑屏,要结合setVisible(false/true)调用, 详见RestTimer.MainWindow
     */
    public static void setWindowStyles(Window window, int value) throws Exception {
        HWND hwnd = getHwnd(window);
        int res = User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE, value);
        //log.info("value={}, res={}", value, res);
    }

    /**
     * 获取窗口的hwnd
     *
     * @param window 必须是setVisible=true,且后续无调用过dispose;只要符合前者,setVisible=false不会影响
     */
    private static HWND getHwnd(Window window) throws ReflectiveOperationException {
        Field fieldPeer = Component.class.getDeclaredField("peer");
        fieldPeer.setAccessible(true);
        Object peer0 = fieldPeer.get(window);
        WComponentPeer peer = (WComponentPeer) peer0;
        if (peer == null) throw new IllegalArgumentException("代码异常,窗口未显示");
        long pointer = peer.getHWnd();
        return new HWND(new Pointer(pointer));
    }

    public interface VisibleListener {

        /**
         * 在[setVisible(false),点关闭按钮,dispose()]后,到展示完后触发
         */
        void onShown();

        /**
         * 隐藏窗口后触发
         *
         * @param dispose true-点关闭按钮,dispose();false-setVisible(false)
         * @apiNote 要正确监听, 必须设置setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);或者重写 windowClosing 方法为dispose;
         * 否则 dispose是不对的
         */
        void onHidden(boolean dispose);
    }

    /**
     * 窗口相对于屏幕的位置
     */
    public enum Gravity {
        TOP_L(1, "左上"),
        TOP_CENTER(2, "顶部"),
        TOP_R(3, "右上"),
        MID_L(4, "靠左"),
        MID_CENTER(5, "居中"),
        MID_R(6, "靠右"),
        BOTTOM_L(7, "左下"),
        BOTTOM_CENTER(8, "底部"),
        BOTTOM_R(9, "右下");

        public final int value;
        public final String text;

        Gravity(int value, String text) {
            this.value = value;
            this.text = text;
        }

        public static Gravity get(int value) {
            for (Gravity gravity : values()) {
                if (gravity.value == value) return gravity;
            }
            throw new IllegalArgumentException("Gravity取值异常:" + value);
        }

        public static Gravity get(int value, Gravity defValue) {
            for (Gravity gravity : values()) {
                if (gravity.value == value) return gravity;
            }
            return defValue;
        }

    }

}
